package com.gitee.wsl.compose.chart.geometry.model

import com.gitee.wsl.common.ui.base.Point
import com.gitee.wsl.ext.base.Arrays.arraycopy


/*
 * Copyright 2013 Hannes Janetzek
 *
 * This file is part of the OpenScienceMap project (http://www.opensciencemap.org).
 *
 * This program is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License along with
 * this program. If not, see <http://www.gnu.org/licenses/>.
 */

/* TODO
 * - check indexPos < Short.Max
 * - should make internals private, maybe
 */
/* TODO
 * - check indexPos < Short.Max
 * - should make internals private, maybe
 */
/**
 * The GeometryBuffer class holds temporary geometry data for processing.
 * Only One geometry type can be set at a time. Use 'clear()' to reset the
 * internal state.
 *
 *
 * 'points[]' holds interleaved x,y coordinates
 *
 *
 * 'index[]' is used to store number of points within a geometry and encode
 * multi-linestrings and (multi-)polygons.
 */
open class GeometryBuffer(points: FloatArray?, index: IntArray?) {
    /**
     * The Enum GeometryType.
     */
    enum class GeometryType(val nativeInt: Int) {
        NONE(0),
        POINT(1),
        LINE(2),
        POLY(3),
        TRIS(4)
    }

    /** The points.  */
    var points: FloatArray

    /** The indexes.  */
    var index: IntArray

    /** The current index position.  */
    var indexPos: Int

    /** The current position in points array.  */
    var pointPos: Int

    /** The current geometry type.  */
    var type: GeometryType
    private val mTmpPoint = Point()
    private var pointLimit: Int

    /**
     * Instantiates a new geometry buffer.
     *
     * @param points the points
     * @param index the index
     */
    init {
        var points = points
        var index = index
        if (points == null) points = FloatArray(GROW_POINTS)
        if (index == null) index = IntArray(GROW_INDICES)
        this.points = points
        this.index = index
        type = GeometryType.NONE
        indexPos = 0
        pointPos = 0
        pointLimit = points.size - 2
    }

    /**
     * @param out PointF to set coordinates to.
     * @return when out is null a temporary PointF is
     * returned which belongs to GeometryBuffer.
     */
    /*fun getPoint(i: Int, out: Point) {
        out.x = points[i shl 1]
        out.y = points[(i shl 1) + 1]
    }*/

    fun getPointX(i: Int): Float {
        return points[i shl 1]
    }

    fun getPointY(i: Int): Float {
        return points[(i shl 1) + 1]
    }

    /**
     * @return PointF belongs to GeometryBuffer.
     */
    fun getPoint(i: Int): Point {
        /*val out = mTmpPoint
        out.x = points[i shl 1]
        out.y = points[(i shl 1) + 1]*/
        return Point( points[i shl 1],points[(i shl 1) + 1])
    }

    val numPoints: Int
        get() = pointPos shr 1

    /**
     * Instantiates a new geometry buffer.
     *
     * @param numPoints the num points
     * @param numIndices the num indices
     */
    constructor(numPoints: Int, numIndices: Int) : this(
        FloatArray(numPoints * 2),
        IntArray(numIndices)
    )

    /**
     * Reset buffer.
     */
    open fun clear() {
        index[0] = 0
        indexPos = 0
        pointPos = 0
        type = GeometryType.NONE
    }

    /**
     * Adds a point with the coordinate x, y.
     *
     * @param x the x ordinate
     * @param y the y ordinate
     */
    fun addPoint(x: Float, y: Float): GeometryBuffer {
        if (pointPos > pointLimit) ensurePointSize((pointPos shr 1) + 1, true)
        points[pointPos++] = x
        points[pointPos++] = y
        index[indexPos] += 2
        return this
    }

    val isPoly: Boolean
        get() = type == GeometryType.POLY
    val isLine: Boolean
        get() = type == GeometryType.LINE
    val isPoint: Boolean
        get() = type == GeometryType.POINT

    /**
     * Sets the point x,y at position pos.
     *
     * @param pos the pos
     * @param x the x ordinate
     * @param y the y ordinate
     */
    fun setPoint(pos: Int, x: Float, y: Float) {
        points[(pos shl 1) + 0] = x
        points[(pos shl 1) + 1] = y
    }

    /**
     * Set geometry type for points.
     */
    fun startPoints() {
        setOrCheckMode(GeometryType.POINT)
    }

    /**
     * Start a new line. Sets geometry type for lines.
     */
    fun startLine(): GeometryBuffer {
        setOrCheckMode(GeometryType.LINE)

        /* ignore */if (index[indexPos] > 0) {

            /* start next */
            if (index[0] >= 0 && ++indexPos >= index.size) ensureIndexSize(indexPos, true)

            /* initialize with zero points */index[indexPos] = 0
        }

        /* set new end marker */if (index.size > indexPos + 1) index[indexPos + 1] = -1
        return this
    }

    /**
     * Start a new polygon. Sets geometry type for polygons.
     */
    fun startPolygon(): GeometryBuffer {
        val start = type == GeometryType.NONE
        setOrCheckMode(GeometryType.POLY)
        if (indexPos + 3 > index.size) ensureIndexSize(indexPos + 2, true)
        if (!start && index[indexPos] != 0) {
            /* end polygon */
            index[++indexPos] = 0

            /* next polygon start */indexPos++
        }

        /* initialize with zero points */index[indexPos] = 0

        /* set new end marker */if (index.size > indexPos + 1) index[indexPos + 1] = -1
        return this
    }

    /**
     * Starts a new polygon hole (inner ring).
     */
    fun startHole() {
        checkMode(GeometryType.POLY)
        if (indexPos + 2 > index.size) ensureIndexSize(indexPos + 1, true)

        /* initialize with zero points */index[++indexPos] = 0

        /* set new end marker */if (index.size > indexPos + 1) index[indexPos + 1] = -1
    }

    fun translate(dx: Float, dy: Float): GeometryBuffer {
        var i = 0
        while (i < pointPos) {
            points[i] += dx
            points[i + 1] += dy
            i += 2
        }
        return this
    }

    fun scale(scaleX: Float, scaleY: Float): GeometryBuffer {
        var i = 0
        while (i < pointPos) {
            points[i] *= scaleX
            points[i + 1] *= scaleY
            i += 2
        }
        return this
    }

    /**
     * Ensure that 'points' array can hold the number of points.
     *
     * @param size the number of points to hold
     * @param copy the the current data when array is reallocated
     * @return the float[] array holding current coordinates
     */
    fun ensurePointSize(size: Int, copy: Boolean): FloatArray {
        var size = size
        if (size * 2 < points.size) return points
        size = size * 2 + GROW_POINTS
        val newPoints = FloatArray(size)
        if (copy) arraycopy(points, 0, newPoints, 0, points.size)
        points = newPoints
        pointLimit = size - 2
        return points
    }

    /**
     * Ensure index size.
     *
     * @param size the size
     * @param copy the copy
     * @return the short[] array holding current index
     */
    fun ensureIndexSize(size: Int, copy: Boolean): IntArray {
        if (size < index.size) return index
        val newIndex = IntArray(size + GROW_INDICES)
        if (copy) arraycopy(index, 0, newIndex, 0, index.size)
        index = newIndex
        return index
    }

    private fun setOrCheckMode(m: GeometryType) {
        if (type == m) return
        require(type == GeometryType.NONE) { "not cleared $m<>$type" }
        type = m
    }

    private fun checkMode(m: GeometryType) {
        require(type == m) { "not cleared $m<>$type" }
    }

    fun addPoint(p: Point) {
        addPoint(p.x, p.y)
    }

    /*fun addPoint(p: Point) {
        addPoint(p.x, p.y)
    }*/

    /**
     * Remove points with distance less than minSqDist
     *
     * TODO could avoid superfluous copying
     *
     * @param minSqDist
     * @param keepLines keep endpoint when line would
     * otherwise collapse into a single point
     */
    fun simplify(minSqDist: Float, keepLines: Boolean) {
        var outPos = 0
        var inPos = 0
        for (idx in index.indices) {
            if (index[idx] < 0) break
            if (index[idx] == 0) continue
            val first = inPos
            var px = points[inPos++]
            var py = points[inPos++]

            /* add first point */points[outPos++] = px
            points[outPos++] = py
            var cnt = 2
            var pt = 2
            val end = index[idx]
            while (pt < end) {
                val cx = points[inPos++]
                val cy = points[inPos++]
                val dx = cx - px
                val dy = cy - py
                if (dx * dx + dy * dy < minSqDist) {
                    if (!keepLines || pt < end - 2) {
                        pt += 2
                        continue
                    }
                }
                px = cx
                py = cy
                points[outPos++] = cx
                points[outPos++] = cy
                cnt += 2
                pt += 2
            }
            if (type == GeometryType.POLY && points[first] == px && points[first + 1] == py) {
                /* remove identical start/end point */
                cnt -= 2
                outPos -= 2
            }
            index[idx] = cnt
        }
    }

    override fun toString(): String {
        val sb = StringBuilder()
        var o = 0
        for (i in index.indices) {
            if (index[i] < 0) break
            if (index[i] == 0) continue
            sb.append(":")
            sb.append(index[i])
            sb.append('\n')
            var j = 0
            while (j < index[i]) {
                sb.append('[')
                    .append(points[o + j])
                    .append(',')
                    .append(points[o + j + 1])
                    .append(']')
                if (j % 4 == 0) sb.append('\n')
                j += 2
            }
            sb.append('\n')
            o += index[i]
        }
        return sb.toString()
    }

    companion object {
        private const val GROW_INDICES = 64
        private const val GROW_POINTS = 512
    }
}

